﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Fintech.EntityFrameworkEngine;
using Fintech.Utils;
using Fintech.Utils.Database;
using Fintech.Utils.Diagnostics;
using ZOVNP.Model.Abstracts;

namespace ZOVNP.Engine
{
    /// <summary> Базовый класс понадобился, чтобы получать из него ObjectQuery, не используя шаблонный параметр. </summary>
    internal abstract class ObjectQueryBase
    {
        #region Поля

        /// <summary> Объектный запрос. </summary>
        private ObjectQuery query;

        #endregion

        #region Конструкторы

        /// <summary> Конструктор с нетипизированным запросом. </summary>
        /// <param name="Query"> Исходный нетипизированный запрос. </param>
        protected ObjectQueryBase(ObjectQuery Query) { query = Query; }

        #endregion

        #region Свойства

        /// <summary> Объектный запрос. </summary>
        public ObjectQuery Query { get { return query; } }

        /// <summary> Текущая используемая объектным запросом политика сочетания получаемых объектов с уже существующими. </summary>
        public MergingOption MergeOption
        {
            get { return ConvertBack(Query.MergeOption); }
            set { Query.MergeOption = Convert(value); }
        }

        #endregion

        #region Методы

        internal static MergeOption Convert(MergingOption Option)
        {
            switch (Option)
            {
                case MergingOption.AppendOnly: return System.Data.Objects.MergeOption.AppendOnly;
                case MergingOption.NoTracking: return System.Data.Objects.MergeOption.NoTracking;
                case MergingOption.OverwriteChanges: return System.Data.Objects.MergeOption.OverwriteChanges;
                case MergingOption.PreserveChanges: return System.Data.Objects.MergeOption.PreserveChanges;
                default: throw new NotSupportedException();
            }
        }

        internal static MergingOption ConvertBack(MergeOption Option)
        {
            switch (Option)
            {
                case System.Data.Objects.MergeOption.AppendOnly: return MergingOption.AppendOnly;
                case System.Data.Objects.MergeOption.NoTracking: return MergingOption.NoTracking;
                case System.Data.Objects.MergeOption.OverwriteChanges: return MergingOption.OverwriteChanges;
                case System.Data.Objects.MergeOption.PreserveChanges: return MergingOption.PreserveChanges;
                default: throw new NotSupportedException();
            }
        }

        #endregion
    }

    /// <summary> Класс для работы с типизированными запросами в runtime для сущностей Entity Framework. </summary>
    /// <typeparam name="TOut"> Тип объектов на выходе запроса. </typeparam>
    /// <typeparam name="TIn"> Тип объектов, используемых внутри запроса. </typeparam>
    internal class ZOVNPObjectQuery<TOut, TIn> : ObjectQueryBase, IPlainQuery, IObjectQuery<TOut>
    {
        #region Поля

        /// <summary> Признак упорядоченности набора данных. </summary>
        private bool isOrdered;
        /// <summary> Метод <c>Queryable.Join(...)</c> с 5 параметрами, используемый для реализации метода <c>IObjectQuery.Join(...)</c>. </summary>
        private static MethodInfo joinGenericMethod = typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static).
               Single(it => it.Name == "Join" && it.GetParameters().Length == 5);

        #endregion

        #region Конструкторы

        /// <summary> Конструктор с нетипизированным запросом. </summary>
        /// <param name="Query"> Исходный нетипизированный запрос. </param>
        /// <param name="IsOrdered"> True, если запрос уже упорядочен, иначе false. </param>
        public ZOVNPObjectQuery(ObjectQuery Query, bool IsOrdered)
            : base(Query)
        {
            Query.CheckOnInheritance<ObjectQuery<TIn>>();
            isOrdered = IsOrdered;
        }

        /// <summary> Конструктор с типизированным запросом. </summary>
        /// <param name="Query"> Исходный типизированный запрос. </param>
        /// <param name="IsOrdered"> True, если запрос уже упорядочен, иначе false. </param>
        public ZOVNPObjectQuery(ObjectQuery<TIn> Query, bool IsOrdered)
            : base(Query)
        {
            Query.CheckOnNull("Query");
            isOrdered = IsOrdered;
        }

        #endregion

        #region Свойства

        /// <summary> Объектный запрос. </summary>
        public new ObjectQuery<TIn> Query { get { return (ObjectQuery<TIn>)base.Query; } }

        /// <summary> Движок для доступа к данным. </summary>
        private ZOVNPEngine Engine { get { return ((ZOVNPContext)Query.Context).Engine; } }

        #endregion

        #region Методы

        /// <summary> Проверить тип выходных данных на поддуржку интерфейса <c>IDomainObject</c>. </summary>
        private static void AssertDomainObjectType(Type CheckedType = null)
        {
            if (CheckedType == null) CheckedType = typeof(TOut);
            if (!CheckedType.SupportsInterface(typeof(IDomainObject)))
                throw new InvalidOperationException(string.Format(Properties.Resources.MSG_InvalidQueryType, CheckedType, typeof(IDomainObject)));
        }

        /// <summary> Вход в критическую секцию обращений к базе данных. </summary>
        private void EnterDatabaseCriticalSection(out bool LockTaken) { Engine.EnterDatabaseCriticalSection(out LockTaken); }

        /// <summary> Выход из критической секции обращений к базе данных. </summary>
        private void ExitDatabaseCriticalSection() { Engine.ExitDatabaseCriticalSection(); }

        private static bool TryGetCollectionImplementationType(Type Source, out Type Result)
        {
            Result = null;
            if (!Source.IsGenericType || Source.GetGenericTypeDefinition() != typeof(IDomainCollection<>)) return false;
            var genericArgs = Source.GetGenericArguments();
            return ZOVNPMetadata.TypeMapping.TryGetForwardValue(genericArgs[0], out Result);
        }

        /// <summary> Получить объекты заданного типа и все связанные с данным запросом объекты из цепочки выборки. </summary>
        /// <param name="IncludePath"> Цепочка выборки связанных объектов в виде лямбда-выражения. </param>
        /// <returns> Интерфейс для выборки объектов. </returns>
        public IObjectQuery<TOut> Include(Expression<Func<TOut, object>> IncludePath)
        {
            var casted = IncludePath.Body as UnaryExpression;
            var currentExpr = (casted != null && casted.NodeType == ExpressionType.Convert ? casted.Operand : IncludePath.Body) as MemberExpression;

            if (currentExpr == null) throw new ArgumentException(Properties.Resources.MSG_PropertiesChain, "IncludePath");
            var sb = new System.Text.StringBuilder();
            while (true)
            {
                var toProperty = currentExpr.Member as System.Reflection.PropertyInfo;
                if (toProperty == null)
                    throw new ArgumentException(string.Format(Properties.Resources.MSG_PropertiesChain + " Значение {0} недопустимо.", currentExpr.Member.Name), "IncludePath");
                //Type implType = null, propType = toProperty.PropertyType;
                //if (!SkifBPMetadata.TypeMapping.TryGetForwardValue(propType, out implType))
                //    if (!TryGetCollectionImplementationType(propType, out implType))
                //        foreach (var iface in propType.GetInterfaces())
                //            if (TryGetCollectionImplementationType(iface, out implType)) break;
                //if (implType == null)
                //    throw new ArgumentException(string.Format(Properties.Resources.MSG_PropertiesChain + " Тип {0} свойства {1} недопустим.", propType, toProperty.Name), "IncludePath");
                //sb.Insert(0, '.').Insert(0, implType.Name);
                sb.Insert(0, '.').Insert(0, toProperty.Name);

                /*** Этот блок кода добавляет поддержку вовлечения в результат запроса связанных объектов на стороне "многие" при обращении со стороны "один". ***/
                var toCallExpression = currentExpr.Expression as MethodCallExpression;
                if (toCallExpression != null && toCallExpression.Method.Name == "MarkIncluded")
                {
                    currentExpr = (MemberExpression)toCallExpression.Object;
                    continue;
                }
                /*** Конец блока. ***/

                var toMemberExpr = currentExpr.Expression as MemberExpression;
                if (toMemberExpr != null)
                    currentExpr = (MemberExpression)currentExpr.Expression;
                else
                {
                    var toParameterExpr = currentExpr.Expression as ParameterExpression;
                    if (toParameterExpr == null)
                        throw new ArgumentException(Properties.Resources.MSG_PropertiesChain, "IncludePath");
                    else if (toParameterExpr.Name == IncludePath.Parameters[0].Name)
                        break;
                    else throw new ArgumentException(string.Format(Properties.Resources.MSG_UnknownParameterInPropertiesChain, toParameterExpr.Name), "IncludePath");
                }
            }
            sb.Length -= 1; // удаление самой первой вставленной точки-разделителя
            return new ZOVNPObjectQuery<TOut, TIn>(Query.Include(sb.ToString()), isOrdered);
        }

        /// <summary> Ограничить результат запроса только уникальными объектами. </summary>
        /// <returns> Построитель запроса. </returns>
        public IObjectQuery<TOut> Distinct() { return new ZOVNPObjectQuery<TOut, TIn>(Query.Distinct(), isOrdered); }

        /// <summary> Упорядочение результатов. </summary>
        /// <typeparam name="TKey"> Тип свойства, по которому идёт упорядочение. </typeparam>
        /// <param name="KeySelector"> Дескриптор свойства, по которому идёт упорядочение, в виде лямбда-выражения. </param>
        /// <returns> Упорядоченный набор объектов. </returns>
        public IObjectQuery<TOut> OrderBy<TKey>(Expression<Func<TOut, TKey>> KeySelector)
        {
            var adapter = new LambdaExpressionAdapter(typeof(TIn));
            var adaptedExpression = (Expression<Func<TIn, TKey>>)adapter.Visit(KeySelector);
            return new ZOVNPObjectQuery<TOut, TIn>(isOrdered ? (ObjectQuery)Query.ThenBy(adaptedExpression) : (ObjectQuery)Query.OrderBy(adaptedExpression), true);
        }

        /// <summary> Выполнение группировки. </summary>
        /// <typeparam name="TKey"> Тип ключа группировки. </typeparam>
        /// <param name="KeySelector"> Выражение для группирующего свойства. </param>
        /// <returns> Сгруппированные объекты. </returns>
        public IObjectQuery<IGrouping<TKey, TOut>> GroupBy<TKey>(Expression<Func<TOut, TKey>> KeySelector)
        {
            var adapter = new LambdaExpressionAdapter(typeof(TIn));
            var adaptedExpression = (Expression<Func<TIn, TKey>>)adapter.Visit(KeySelector);
            return new ZOVNPObjectQuery<IGrouping<TKey, TOut>, IGrouping<TKey, TIn>>((ObjectQuery)Query.GroupBy(adaptedExpression), isOrdered);
        }

        /// <summary> Пропустить несколько объектов. </summary>
        /// <param name="Count"> Число пропускаемых объектов. </param>
        /// <returns> Построитель запроса. </returns>
        /// <remarks> Для использования этого метода необходимо сначала вызвать метод <c>OrderBy</c>. </remarks>
        public IObjectQuery<TOut> Skip(int Count)
        {
            if (Count <= 0) throw new ArgumentException(Properties.Resources.MSG_ValueMustBeGreaterThanZero, "Count");
            return new ZOVNPObjectQuery<TOut, TIn>((ObjectQuery)Query.Skip(Count), isOrdered);
        }

        /// <summary> Ограничить результат запроса. </summary>
        /// <param name="Count"> Ограничение на число возвращаемых объектов. </param>
        /// <returns> Построитель запроса. </returns>
        public IObjectQuery<TOut> Take(int Count)
        {
            if (Count <= 0) throw new ArgumentException(Properties.Resources.MSG_ValueMustBeGreaterThanZero, "Count");
            return new ZOVNPObjectQuery<TOut, TIn>((ObjectQuery)Query.Take(Count), isOrdered);
        }

        /// <summary> Сузить выборку до значений, удовлетворяющих заданному условию. </summary>
        /// <param name="Predicate"> Условие в виде лямбда-выражения. </param>
        /// <returns> Построитель запроса. </returns>
        public IObjectQuery<TOut> Where(Expression<Func<TOut, bool>> Predicate)
        {
            var adapter = new LambdaExpressionAdapter(typeof(TIn));
            var adaptedExpression = (Expression<Func<TIn, bool>>)adapter.Visit(Predicate);
            return new ZOVNPObjectQuery<TOut, TIn>((ObjectQuery)Query.Where(adaptedExpression), isOrdered);
        }

        /// <summary> Выполнить проекцию. </summary>
        /// <typeparam name="TResult"> Тип данных проекции. </typeparam>
        /// <param name="Selector"> Выражение, задающее проекцию. </param>
        /// <returns> Набор спроецированных данных. </returns>
        public IObjectQuery<TResult> Select<TResult>(Expression<Func<TOut, TResult>> Selector) // where TResult : new() /* но тогда компилятор ругается на строки */
        {
            if (Selector.ReturnType.IsEnum) throw new NotSupportedException(Properties.Resources.MSG_EnumProjectionNotSupported);
            ((ZOVNPContext)Query.Context).GuardDispose();
            var adapter = new LambdaExpressionAdapter(typeof(TIn));
            var adaptedExpression = (Expression<Func<TIn, TResult>>)adapter.Visit(Selector);
            var selectQuery = (ObjectQuery<TResult>)Query.Select(adaptedExpression);
            var newQuery = typeof(ZOVNPObjectQuery<,>).MakeGenericType(typeof(TResult), adaptedExpression.ReturnType).
                GetConstructor(new[] { typeof(ObjectQuery), typeof(bool) }).Invoke(new object[] { selectQuery, isOrdered });
            return (IObjectQuery<TResult>)newQuery;
        }

        /// <summary> Объединение результатов двух объектных запросов. </summary>
        /// <typeparam name="TEntity"> Тип данных в объектном запросе, с которым будет объединён данный запрос. </typeparam>
        /// <typeparam name="TKey"> Тип ключа, по которому происходит соединение. </typeparam>
        /// <typeparam name="TResult"> Тип возвращаемых объектов. </typeparam>
        /// <param name="OtherObjectQuery"> Объектный запрос, с которым будет объединён данный запрос. </param>
        /// <param name="ThisKeySelector"> Выражение, задающее ключ первого объединяемого запроса. </param>
        /// <param name="OtherKeySelector"> Выражение, задающее ключ второго объединяемого запроса. </param>
        /// <param name="ResultSelector"> Выражение, задающее выходной результат. </param>
        /// <returns> Построитель запроса. </returns>
        public IObjectQuery<TResult> Join<TEntity, TKey, TResult>(IObjectQuery<TEntity> OtherObjectQuery, Expression<Func<TOut, TKey>> ThisKeySelector,
            Expression<Func<TEntity, TKey>> OtherKeySelector, Expression<Func<TOut, TEntity, TResult>> ResultSelector)
        {
            OtherObjectQuery.CheckOnNull("OtherObjectQuery");
            ThisKeySelector.CheckOnNull("ThisKeySelector");
            OtherKeySelector.CheckOnNull("OtherKeySelector");
            ResultSelector.CheckOnNull("ResultSelector");

            Type otherType = typeof(TEntity), implType = null;
            if (!ZOVNPMetadata.TypeMapping.TryGetForwardValue(otherType, out implType))
                throw new ServiceNotSupportedException(typeof(IObjectQuery<>).MakeGenericType(otherType));

            var keySel1 = new LambdaExpressionAdapter(typeof(TIn)).Visit(ThisKeySelector);
            var seySel2 = new LambdaExpressionAdapter(implType).Visit(OtherKeySelector);
            var resSel = new LambdaExpressionAdapter(typeof(TIn), implType).Visit(ResultSelector);

            var joinNotGenericMethod = joinGenericMethod.MakeGenericMethod(typeof(TIn), implType, typeof(TKey), typeof(TResult));
            var joinQuery = (ObjectQuery<TResult>)joinNotGenericMethod.Invoke(null, new object[] { Query, ((ObjectQueryBase)OtherObjectQuery).Query, keySel1, seySel2, resSel });
            var newQueryType = typeof(ZOVNPObjectQuery<,>).MakeGenericType(typeof(TResult), ((LambdaExpression)resSel).ReturnType);
            var newQuery = newQueryType.GetConstructor(new[] { typeof(ObjectQuery), typeof(bool) }).Invoke(new object[] { joinQuery, isOrdered });

            return (IObjectQuery<TResult>)newQuery;
        }

        /// <summary> Получить объект по его уникальному идентификатору. </summary>
        /// <param name="Key"> Уникальный идентификатор объекта. </param>
        /// <param name="Count"> Ограничение на число возвращаемых объектов. </param>
        /// <returns> Объект заданного типа. </returns>
        public TOut One(Guid Key) { return One(Key, MergeOption); }

        /// <summary> Получить объект по его уникальному идентификатору. </summary>
        /// <param name="Key"> Уникальный идентификатор объекта. </param>
        /// <param name="MergeOption"> Определяет, как объекты, загружаемые объектным запросом, объединяются с объектами, которые уже были загружены ранее. </param>
        /// <returns> Объект заданного типа. </returns>
        public TOut One(Guid Key, MergingOption MergeOption)
        {
            ((ZOVNPContext)Query.Context).GuardDispose();
            AssertDomainObjectType();
            IEnumerator<TOut> enumerator = null;
            while (true)
            {
                var temp = new ZOVNPObjectQuery<TOut, TIn>(Query.Where("it.ID = @id", new ObjectParameter("id", Key)), isOrdered);
                DebugTuner.Instance.Logger.WriteLine(() => Engine.ProviderAdapter.ToTraceString(temp.Query));
                bool entered = false; // признак удачного захвата блокировки
                try
                {
                    EnterDatabaseCriticalSection(out entered);
                    enumerator = temp.GetInternalEnumerator(false, MergeOption);
                    if (enumerator.MoveNext()) return enumerator.Current;
                    else return default(TOut);
                }
                catch (NestedQueryInTheSameThreadException) { entered = false; throw; }
                catch (System.Threading.ThreadAbortException) { }
                catch (Exception Ex) { if (!((ZOVNPContext)Query.Context).Engine.HandleException(Ex, (ZOVNPContext)Query.Context)) throw; }
                finally
                {
                    if (enumerator != null) enumerator.Dispose();
                    if (entered) ExitDatabaseCriticalSection();
                }
            }
        }

        /// <summary> Получить все объекты. </summary>
        /// <returns> Массив объектов. </returns>
        public TOut[] ToArray() { return ToArray(MergeOption); }

        /// <summary> Получить все объекты. </summary>
        /// <param name="MergeOption"> Определяет, как объекты, загружаемые объектным запросом, объединяются с объектами, которые уже были загружены ранее. </param>
        /// <returns> Массив объектов. </returns>
        public TOut[] ToArray(MergingOption MergeOption)
        {
            ((ZOVNPContext)Query.Context).GuardDispose();
            IEnumerator<TOut> enumerator = null;
            while (true)
            {
                DebugTuner.Instance.Logger.WriteLine(() => Engine.ProviderAdapter.ToTraceString(Query));
                bool entered = false; // признак удачного захвата блокировки
                try
                {
                    EnterDatabaseCriticalSection(out entered);
                    var list = new List<TOut>();
                    enumerator = GetInternalEnumerator(false, MergeOption);
                    while (enumerator.MoveNext())
                        list.Add(enumerator.Current);
                    return list.ToArray();
                }
                catch (NestedQueryInTheSameThreadException) { entered = false; throw; }
                catch (System.Threading.ThreadAbortException) { }
                catch (Exception Ex)
                {
                    if (!((ZOVNPContext)Query.Context).Engine.HandleException(Ex, (ZOVNPContext)Query.Context))
                        throw;
                }
                finally
                {
                    if (enumerator != null) enumerator.Dispose();
                    if (entered) ExitDatabaseCriticalSection();
                }
            }
        }

        /// <summary> Получить все объекты и занести их в список. </summary>
        /// <typeparam name="T"> Тип объектов. </typeparam>
        /// <returns> Список объектов. </returns>
        public List<TOut> ToList() { return ToList(MergeOption); }

        /// <summary> Получить все объекты и занести их в список. </summary>
        /// <param name="MergeOption"> Определяет, как объекты, загружаемые объектным запросом, объединяются с объектами, которые уже были загружены ранее. </param>
        /// <returns> Список объектов. </returns>
        public List<TOut> ToList(MergingOption MergeOption)
        {
            ((ZOVNPContext)Query.Context).GuardDispose();
            IEnumerator<TOut> enumerator = null;
            while (true)
            {
                DebugTuner.Instance.Logger.WriteLine(() => Engine.ProviderAdapter.ToTraceString(Query));
                bool entered = false; // признак удачного захвата блокировки
                try
                {
                    EnterDatabaseCriticalSection(out entered);
                    var list = new List<TOut>();
                    enumerator = GetInternalEnumerator(false, MergeOption);
                    while (enumerator.MoveNext())
                        list.Add(enumerator.Current);
                    return list;
                }
                catch (NestedQueryInTheSameThreadException) { entered = false; throw; }
                catch (System.Threading.ThreadAbortException) { }
                catch (Exception Ex) { if (!((ZOVNPContext)Query.Context).Engine.HandleException(Ex, (ZOVNPContext)Query.Context)) throw; }
                finally
                {
                    if (enumerator != null) enumerator.Dispose();
                    if (entered) ExitDatabaseCriticalSection();
                }
            }
        }

        /// <summary> Получить первую сущность запроса. </summary>
        /// <returns> Первая сущность, удовлетворяющая запросу. </returns>
        /// <remarks> Этот метод извлекает из хранилища именно 1 сущность (или 0), тем самым снижая затраты памяти на материализацию объектов. </remarks>
        /// <exception cref="InvalidOperationException"> Возвращённый запросом набор данных пуст. </exception>
        public TOut First() { return First(MergeOption); }

        /// <summary> Получить первую сущность запроса. </summary>
        /// <param name="MergeOption"> Определяет, как объекты, загружаемые объектным запросом, объединяются с объектами, которые уже были загружены ранее. </param>
        /// <returns> Первая сущность, удовлетворяющая запросу. </returns>
        /// <remarks> Этот метод извлекает из хранилища именно 1 сущность (или 0), тем самым снижая затраты памяти на материализацию объектов. </remarks>
        /// <exception cref="InvalidOperationException"> Возвращённый запросом набор данных пуст. </exception>
        public TOut First(MergingOption MergeOption)
        {
            ((ZOVNPContext)Query.Context).GuardDispose();
            IEnumerator<TOut> enumerator = null;
            TOut result = default(TOut);
            bool exists = true;
            while (true)
            {
                var temp = (ZOVNPObjectQuery<TOut, TIn>)Take(1);
                DebugTuner.Instance.Logger.WriteLine(() => Engine.ProviderAdapter.ToTraceString(temp.Query));
                bool entered = false; // признак удачного захвата блокировки
                try
                {
                    EnterDatabaseCriticalSection(out entered);
                    enumerator = temp.GetInternalEnumerator(false, MergeOption);
                    if (exists = enumerator.MoveNext())
                        result = enumerator.Current;
                    break;
                }
                catch (NestedQueryInTheSameThreadException) { entered = false; throw; }
                catch (System.Threading.ThreadAbortException) { }
                catch (Exception Ex) { if (!((ZOVNPContext)Query.Context).Engine.HandleException(Ex, (ZOVNPContext)Query.Context)) throw; }
                finally
                {
                    if (enumerator != null) enumerator.Dispose();
                    if (entered) ExitDatabaseCriticalSection();
                }
            }
            if (exists) return result;
            else throw new InvalidOperationException(Properties.Resources.MSG_EmptyResult);
        }

        /// <summary> Получить первую сущность запроса. </summary>
        /// <param name="Predicate"> Условие в виде лямбда-выражения. </param>
        /// <returns> Первая сущность, удовлетворяющая запросу. </returns>
        /// <remarks> Этот метод извлекает из хранилища именно 1 сущность (или 0), тем самым снижая затраты памяти на материализацию объектов. </remarks>
        public TOut First(Expression<Func<TOut, bool>> Predicate) { return Where(Predicate).First(); }

        /// <summary> Получить первую сущность запроса. </summary>
        /// <param name="Predicate"> Условие в виде лямбда-выражения. </param>
        /// <param name="MergeOption"> Определяет, как объекты, загружаемые объектным запросом, объединяются с объектами, которые уже были загружены ранее. </param>
        /// <returns> Первая сущность, удовлетворяющая запросу. </returns>
        /// <remarks> Этот метод извлекает из хранилища именно 1 сущность (или 0), тем самым снижая затраты памяти на материализацию объектов. </remarks>
        public TOut First(Expression<Func<TOut, bool>> Predicate, MergingOption MergeOption) { return Where(Predicate).First(MergeOption); }

        /// <summary> Получить первую сущность запроса, либо null, если запрос вернул 0 сущностей. </summary>
        /// <returns> Первая сущность, удовлетворяющая запросу, либо null. </returns>
        /// <remarks> Этот метод извлекает из хранилища именно 1 сущность (или 0), тем самым снижая затраты памяти на материализацию объектов. </remarks>
        public TOut FirstOrDefault() { return FirstOrDefault(MergeOption); }

        /// <summary> Получить первую сущность запроса, либо null, если запрос вернул 0 сущностей. </summary>
        /// <param name="MergeOption"> Определяет, как объекты, загружаемые объектным запросом, объединяются с объектами, которые уже были загружены ранее. </param>
        /// <returns> Первая сущность, удовлетворяющая запросу, либо null. </returns>
        /// <remarks> Этот метод извлекает из хранилища именно 1 сущность (или 0), тем самым снижая затраты памяти на материализацию объектов. </remarks>
        public TOut FirstOrDefault(MergingOption MergeOption)
        {
            ((ZOVNPContext)Query.Context).GuardDispose();
            IEnumerator<TOut> enumerator = null;
            while (true)
            {
                var temp = (ZOVNPObjectQuery<TOut, TIn>)Take(1);
                DebugTuner.Instance.Logger.WriteLine(() => Engine.ProviderAdapter.ToTraceString(temp.Query));
                bool entered = false; // признак удачного захвата блокировки
                try
                {
                    EnterDatabaseCriticalSection(out entered);
                    enumerator = temp.GetInternalEnumerator(false, MergeOption);
                    if (enumerator.MoveNext()) return enumerator.Current;
                    else return default(TOut);
                }
                catch (NestedQueryInTheSameThreadException) { entered = false; throw; }
                catch (System.Threading.ThreadAbortException) { }
                catch (Exception Ex) { if (!((ZOVNPContext)Query.Context).Engine.HandleException(Ex, (ZOVNPContext)Query.Context)) throw; }
                finally
                {
                    if (enumerator != null) enumerator.Dispose();
                    if (entered) ExitDatabaseCriticalSection();
                }
            }
        }

        /// <summary> Получить первую сущность запроса, либо null, если запрос вернул 0 сущностей. </summary>
        /// <param name="Predicate"> Условие в виде лямбда-выражения. </param>
        /// <returns> Первая сущность, удовлетворяющая запросу, либо null. </returns>
        /// <remarks> Этот метод извлекает из хранилища именно 1 сущность (или 0), тем самым снижая затраты памяти на материализацию объектов. </remarks
        public TOut FirstOrDefault(Expression<Func<TOut, bool>> Predicate) { return Where(Predicate).FirstOrDefault(); }

        /// <summary> Получить первую сущность запроса, либо null, если запрос вернул 0 сущностей. </summary>
        /// <param name="Predicate"> Условие в виде лямбда-выражения. </param>
        /// <param name="MergeOption"> Определяет, как объекты, загружаемые объектным запросом, объединяются с объектами, которые уже были загружены ранее. </param>
        /// <returns> Первая сущность, удовлетворяющая запросу, либо null. </returns>
        /// <remarks> Этот метод извлекает из хранилища именно одну сущность (или 0), тем самым снижая затраты памяти на материализацию объектов. </remarks>
        public TOut FirstOrDefault(Expression<Func<TOut, bool>> Predicate, MergingOption MergeOption) { return Where(Predicate).FirstOrDefault(MergeOption); }

        private IEnumerator<TOut> GetInternalEnumerator(bool ReleaseLockAfterEnumeratorEnds, MergingOption MergeOption)
        {
            ((ZOVNPContext)Query.Context).GuardDispose();
            var result = Query.Execute(Convert(MergeOption));
            var enumerator = result.Cast<TOut>().GetEnumerator();
            if (ReleaseLockAfterEnumeratorEnds) return new ObjectResultEnumerator<TOut>(result, enumerator, ExitDatabaseCriticalSection);
            return new ObjectResultEnumerator<TOut>(result, enumerator, null);
        }

        /// <summary> Получить итератор результата запроса. </summary>
        /// <returns> Итератор по набору сущностей, удовлетворяющих данному запросу. </returns>
        /// <remarks> Этот итератор после использования в обязательном порядке следует уничтожить. </remarks>
        public IEnumerator<TOut> GetEnumerator()
        {
            bool entered = false;
            EnterDatabaseCriticalSection(out entered); // Специально вне цикла while. Блокировка происходит один раз.
            while (true)
            {
                DebugTuner.Instance.Logger.WriteLine(() => Engine.ProviderAdapter.ToTraceString(Query));
                try { return GetInternalEnumerator(true, MergeOption); }
                catch (System.Threading.ThreadAbortException) { ExitDatabaseCriticalSection(); }
                catch (Exception Ex) { if (!((ZOVNPContext)Query.Context).Engine.HandleException(Ex, (ZOVNPContext)Query.Context)) throw; }
                // Блока finally специально нет. При вызове метода Dispose() над возвращаемым итератором будет вызван метод ExitDatabaseCriticalSection().
            }
        }

        /// <summary> Получить итератор результата запроса. </summary>
        /// <returns> Итератор по набору сущностей, удовлетворяющих данному запросу. </returns>
        /// <remarks> Этот итератор после использования в обязательном порядке следует уничтожить. </remarks>
        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        /// <summary> Фильтрация по типу объектов. </summary>
        /// <typeparam name="TResult"> Тип объектов, остающихся в наборе возвращаемых сущностей. </typeparam>
        /// <returns> Построитель запроса. </returns>
        public IObjectQuery<TResult> OfType<TResult>()
        {
            AssertDomainObjectType();
            AssertDomainObjectType(typeof(TResult));
            Type resType = typeof(TResult), implType = null;
            if (!ZOVNPMetadata.TypeMapping.TryGetForwardValue(resType, out implType))
                throw new ServiceNotSupportedException(typeof(IObjectQuery<>).MakeGenericType(resType));
            var newQuery = typeof(ZOVNPObjectQuery<,>).MakeGenericType(resType, implType).GetConstructor(new[] { typeof(ObjectQuery), typeof(bool) }).
                Invoke(new[] { Query.GetType().GetMethod("OfType").MakeGenericMethod(implType).Invoke(Query, new object[0]), isOrdered });
            return (IObjectQuery<TResult>)newQuery;
        }

        /// <summary> Получить число объектов в созданной выборке. </summary>
        /// <returns> Число объектов, которое возвратила бы выборка при вызове метода <c>ToArray()</c>. </returns>
        public int Count()
        {
            ((ZOVNPContext)Query.Context).GuardDispose();
            while (true)
            {
                DebugTuner.Instance.Logger.WriteLine(() => "SELECT COUNT (*) FROM " + Engine.ProviderAdapter.ToTraceString(Query));
                bool entered = false; // признак удачного захвата блокировки
                try
                {
                    EnterDatabaseCriticalSection(out entered);
                    return Query.Count();
                }
                catch (NestedQueryInTheSameThreadException) { entered = false; throw; }
                catch (System.Threading.ThreadAbortException) { }
                catch (Exception Ex) { if (!((ZOVNPContext)Query.Context).Engine.HandleException(Ex, (ZOVNPContext)Query.Context)) throw; }
                finally { if (entered) ExitDatabaseCriticalSection(); }
            }
        }

        /// <summary> Получить число элементов заданного типа, удовлетворяющих заданному условию. </summary>
        /// <param name="Predicate"> Предикат запроса. </param>
        /// <param name="Distinct"> True, если требуется уникальность подсчитываемых значений. </param>
        /// <param name="Parameters"> Параметры запроса. </param>
        /// <remarks> Для метода <c>CollectionLocker.GetCount()</c>. </remarks>
        /// <returns> Число. </returns>
        internal int GetCount(string Predicate, params ObjectParameter[] Parameters)
        {
            if (string.IsNullOrWhiteSpace(Predicate)) throw new ArgumentException(Properties.Resources.MSG_EmptyPredicate, "Predicate");
            var temp = Query.Where(Predicate, Parameters);
            while (true)
            {
                DebugTuner.Instance.Logger.WriteLine(() => "SELECT COUNT (*) FROM " + Engine.ProviderAdapter.ToTraceString(temp));
                bool entered = false; // признак удачного захвата блокировки
                try
                {
                    EnterDatabaseCriticalSection(out entered);
                    return temp.Count();
                }
                catch (NestedQueryInTheSameThreadException) { entered = false; throw; }
                catch (System.Threading.ThreadAbortException) { }
                catch (Exception Ex) { if (!((ZOVNPContext)Query.Context).Engine.HandleException(Ex, (ZOVNPContext)Query.Context)) throw; }
                finally { if (entered) ExitDatabaseCriticalSection(); }
            }
        }

        /// <summary> Получить все объекты. </summary>
        /// <returns> Массив объектов. </returns>
        IDomainObject[] IPlainQuery.ToArray()
        {
            AssertDomainObjectType();
            return (IDomainObject[])(object)ToArray();
        }

        /// <summary> Получить один объект. </summary>
        /// <param name="EntityID"> Идентификатор объекта. </param>
        /// <returns> Заданный доменный объект или null, если таковой не найден. </returns>
        IDomainObject IPlainQuery.One(Guid EntityID) { return (IDomainObject)One(EntityID); }

        #endregion
    }
}
